iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0

今天要來建立文章列表。這邊會使用到 useQuery() 這個方法,以及為了避免重複觸及 API 兩次的 useAsyncData() 方法。

首先新增 @/pages/articles/index.vue

<template>
  <section class="pb-24">
    <div class="divide-y divide-gray-200 dark:divide-gray-700">
      <header class="pb-8 space-y-2 md:space-y-5">
        <div>
          <h1 class="tracking-tight font-extrabold text-gray-900 text-2xl leading-6 mb-1">
            Aritcles
          </h1>
        </div>
      </header>
      <main>
        <ul>
          <li v-for="article in articles" :key="article._path" class="py-4">
            <ListItem :item="article" />
          </li>
        </ul>
      </main>
    </div>
  </section>
</template>
<script lang="ts" setup>
import { Post } from '@/types/index'

const slug = 'articles'
const { data } = await useAsyncData(slug, () =>
  queryContent(slug)
    .where({ slug: { $ne: slug }, _file: { $not: { $contains: 'index' } } })
    .sort({ created_at: -1, published_at: -1 })
    .find()
)

const post = data.value as Post[]
const articles = post.map((post) => {
  const hasPostTags = !!post.tags
  if (!hasPostTags) {
    post.tags = []
  }
  return post
})
</script>

queryContent() 是協助我們查詢 content 資料夾檔案的方法,其中 slug 這個參數可以先視為要從其下的哪個路徑去查詢。後面則是搭配 MongoDB 的查詢語法去做篩選。

外面則再使用 useAsyncData() 包覆起來,並加上 await 來等待這份資料的查詢。

另外,會發現我將原本放在 @/components/TheArticles.vue 的介面抽出來放到了 @/index.d.ts,讓我可複用這個介面。

在下方並且未有些可能不會有 tags 屬性的文章加上這個屬性,避免在後面因為未定義而出錯。

為了讓程式碼去可讀性,從這裡會將顯示列表的項目拆成另一個 component,位於 @/components/ListItem.vue

<template>
  <article
    class="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0"
  >
    <dl>
      <dt class="sr-only">Published on</dt>
      <dd
        class="text-base font-medium leading-6 text-gray-500"
      >
        <time v-if="datetime" dateTime="{{datetime}}">
          {{
            DateTime.fromISO(datetime).toLocal().toFormat('yyyy-LL-dd')
          }}
        </time>
      </dd>
    </dl>
    <div class="col-span-3">
      <div>
        <NuxtLink :href="item._path">
          <h3
            class="text-xl font-bold leading-8 tracking-tight text-gray-900"
          >
            {{ item.title }}
          </h3>
        </NuxtLink>
        <div class="flex flex-wrap">
          <PostTag v-for="tag in item.tags" :key="tag" :text="tag" />
        </div>
      </div>
    </div>
  </article>
</template>
<script setup lang="ts">
import { DateTime } from 'luxon'
import { Post } from '@/types/index'

const props = defineProps<{
  item: Post
}>()

const datetime = props.item.published_at
  ? props.item.published_at
  : props.item.created_at
</script>

最後再建立 @/components/PostTag.vue 來渲染標籤清單:

<template>
  <NuxtLink
    :href="'/tags/' + kebabCase(text)"
    class="mr-3 text-sm font-medium uppercase text-teal-500 hover:text-teal-600"
  >
    {{ text.split(' ').join('-') }}
  </NuxtLink>
</template>
<script setup>
import { kebabCase } from 'lodash-es/string'

defineProps({
  text: {
    type: String,
    default: '',
  },
})
</script>

成果如下圖:


上一篇
為文章頁面加上日期與標籤
下一篇
為文章列表建立簡單的搜尋功能
系列文
用 Nuxt Content 搭配 Obsidian 建立自己的 Digital Garden30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言